不知道你有沒有遇到這個問題:我是個獨立APP開發者,我想要開發一個APP,我會做會員系統,還需要一個後台去儲存用戶的資料,同時我又想做訂閱的機制。
我就很常遇到這個問題,在於後台的部分我最常使用的就是Firebase,使用Authentication + Cloud Firestore就可以簡單得建立一個會員系統了,這邊就不再贅述。
然而,長期有一個問題困擾著我,我該怎麼做訂閱的機制?
在簡單的研究後我了解到通常在做訂閱系統時會需要一個後台,並且這個後台要與Google play做通訊,確保後台的資料隨時更新到最新的訂閱狀態。
可惜當時的我只研究到這裡,怎麼去做通訊就完全不知道了,因此我試了很多替代方案,例如:用google api隨時去抓取最新的訂閱狀態(但這樣訂閱綁定的是google play帳號,而不是用戶在我APP登入的帳號)、改用單次購買的商品,購買後去更新後台訂閱狀態(可惜這樣無法自動續訂)
直到最近才下定決心去研究訂閱系統的製作,一樣是用Firebase做為後台,接下來會介紹最關鍵的部分:Google play要如何跟Firebase達成通訊
(先打個預防針,訂閱的某些機制我現在也還不是很了解,這邊就是紀錄我最終的作法,歡迎大家提供意見~)
首先,假設你已經寫好一個APP,而且在Google play console也已經建立一個APP
那麼第一步就是先建立新的訂閱,在google play console進入你的APP點擊左側的訂閱,再點擊右上角的"建立訂閱項目"
建立新的訂閱後你還需要在訂閱裡去新增基礎方案(我的APP是用flutter去寫,然而在IAP(In App Purchase)購買時不太清楚有沒有辦法一個訂閱有多個方案並且買特定的方案,所以我都是每個訂閱裡包含一個方案,看有沒有大神可以解答)
接著,你需要到google cloud platform去啟用Pub/Sub
(https://console.cloud.google.com/apis/library/pubsub.googleapis.com)
到Pub/Sub的頁面,點選左上"建立主題"
輸入主題ID然後建立
在你新增的主題右邊點選"新增主體"
新增主題內貼上 google-play-developer-notifications@system.gserviceaccount.com ,角色則是選擇Pub/Sub底下的"發布/訂閱發布者"
複製"主題名稱"
回到google play console左側的"營利設定",把剛剛複製的主題名稱貼上
到這邊google方面已經完成設定了,再來要設定firestore了
首先要先啟用firebase的Blaze方案並啟用functions
啟用functions會有一些簡單的指示告訴你怎麼做
詳細的部屬可以參考官方文件或以下資源:
https://firebase.google.com/docs/functions/get-started?hl=zh-tw&gen=2nd
https://www.letswrite.tw/cloud-functions-init/
https://hackmd.io/@phzeng/r1ahu0cer#%E4%B8%80%E3%80%81%E4%BB%80%E9%BA%BC%E6%98%AFCloud-Function%EF%BC%9F
這邊只簡單介紹一下Firebase functions(好吧,只是我懶),基本上functions是在你的本地端做撰寫,寫完了之後再佈署到firebase,所以這些教學都是教你怎麼在本地init專案及佈署到firebase
當你已經學會佈署functions了,接下來你需要做的是要寫一個function去接收來自google的訂閱通知,基本上firebase已經有提供了必要的package,只要導入就可以輕易地使用了,而我這邊是請Chat-gpt幫忙產出程式碼再自己做微調
在開始寫function之前我們需要先了解一下google 打過來的資料是甚麼格式,下面是一個範例:
{
"version":"1.0",
"packageName":"com.some.thing",
"eventTimeMillis":"1503349566168",
"subscriptionNotification":
{
"version":"1.0",
"notificationType":4,
"purchaseToken":"PURCHASE_TOKEN",
"subscriptionId":"monthly001"
}
}
簡單介紹幾個重要的參數:
packageName: APP的套件名稱
eventTimeMillis:通知的觸發時間
notificationType:訂閱狀態
purchaseToken:訂閱購買時產出的token,除非是新購買不然這個token不會變(可以用這組token找到訂閱者)
subscriptionId:訂閱項目ID(可以知道這個訂閱是哪個訂閱產品)
參考: https://developer.android.com/google/play/billing/rtdn-reference?hl=zh-cn
知道了google的回傳格式我們就可以來寫function了,這邊提供我寫用來接收訂閱資訊的Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const { PubSub, Snapshot } = require('@google-cloud/pubsub');
const pubSubClient = new PubSub();
admin.initializeApp();
//topic內放前面Pub/Sub設定的主題ID
exports.processRenewalNotifications = functions.pubsub.topic('subscription').onPublish((message) => {
try {
// 解碼 Pub/Sub 消息
const messageData = message.data ? Buffer.from(message.data, 'base64').toString() : null;
const data = JSON.parse(messageData);
//用purchaseToken去找出是哪個用戶的訂閱資訊,並判斷是哪個訂閱產品,決定要延長的訂閱時間
const db = admin.firestore();
db.collection('users')
.where('purchaseToken', '==', data.subscriptionNotification.purchaseToken)
.get()
.then((snapshot) => {
if (snapshot.empty) {
return;
}
const productId = data.subscriptionNotification.subscriptionId;
const timeMills = data.eventTimeMillis;
const currentDate = new Date(parseInt(timeMills));
switch (productId) {
case '1month_sub':
currentDate.setMonth(currentDate.getMonth() + 1);
break;
case '6month_sub':
currentDate.setMonth(currentDate.getMonth() + 6);
break;
case '1year_sub':
currentDate.setFullYear(currentDate.getFullYear() + 1);
break;
}
// 設定訂閱週期的隔天0時為下個到期日
currentDate.setDate(currentDate.getDate() + 1);
currentDate.setHours(0, 0, 0, 0);
const newTimestamp = Math.floor(currentDate.getTime());
const type = data.subscriptionNotification.notificationType;
// 去更新用戶的訂閱狀態,我是設定只有notificationType是2的時候才去更新訂閱時間
snapshot.forEach(doc => {
db.collection('users').doc(doc.id).update({
'productId': data.subscriptionNotification.subscriptionId,
'notificationType': data.subscriptionNotification.notificationType,
...(type === 2 ? { 'expire_time': newTimestamp } : {})
});
// 把整筆訂閱資訊記錄下來,有爭議時有個依據
db.collection('users')
.doc(doc.id)
.collection('iap')
.doc(timeMills.toString())
.set(data);
});
})
.catch((error) => { });
return null;
} catch (error) {
console.error('Error processing renewal notification:', error);
throw new functions.https.HttpsError('unknown', 'Failed to process renewal notification', error);
}
});
到這邊就已經完成囉,可以到google play console的"營利設定"點擊傳送測試訊息看有沒有收到喔
最後簡單總結一下邏輯:
購買訂閱:
消費者下單 => google play => 回傳APP下單成功 => APP通知firebase後台下單成功並儲存purchaseToken
訂閱狀態發生改變時(EX:自動續訂):
google play透過Pub/Sub發出通知 => 利用firebase function接收通知 => 用purchaseToken找出用戶並修改訂閱狀態
以上就是訂閱的流程,原本覺得很複雜,但寫下來發現其實也蠻簡單的,另外距離我做出訂閱流程已經過了一段時間了,可能會有些缺漏也不是那麼詳細,還請見諒
最後提一下,我不確定這是不是最佳的訂閱處理流程,但基本邏輯應該沒有問題,若有錯誤的地方也歡迎糾正